// Copyright (C) 2000 BotS Q3 Team
//

/*****************************************************************************
 * name:		bots_team.c
 *
 * desc:		Quake3 Battle of the Sexes Team Routines
 *
 * $Archive: /source/code/game/bots_team.c $
 * $Author: tbp $ 
 * $Revision: 1.4 $
 * $Modtime: 3/1/00 12:00a $
 * $Date: 2001/01/08 05:23:40 $
 *
 *****************************************************************************/

#include "g_local.h"
#include "bots_local.h"
#include "bots_scientist.h"

bots_team_t	bots_teams[BOTS_MAX_TEAMS];
bots_info_t	bots_info;
bots_teamkey_t bots_teamkeys[BOTS_MAX_KEYS];

// New version, that stuff is pre-computed
// And the message spamming is done when updating
qboolean bots_fairTeams (int ai_team) {
	if ( g_bots_fairteams.integer ) {
		int diff = abs(bots_teams[0].members - bots_teams[1].members);
		
		
		if (diff>1) {
			return qfalse;
		}
		else
			return qtrue;

	}
	else
		return qtrue;
}

void bots_setCaptureCount(int ai_team) {
	if (ai_team == TEAM_RED) {
		if (bots_teams[TEAM_BLUE].team_capsinarow)
			bots_teams[TEAM_BLUE].team_capsinarow = 0;
	}
	else {
		if (bots_teams[TEAM_RED].team_capsinarow)
			bots_teams[TEAM_RED].team_capsinarow = 0;
	}

	bots_teams[ai_team].team_capsinarow++;
}


qboolean bots_getQuadRally(int ai_team) {
	if (bots_teams[ai_team].team_capsinarow > 2) {
		bots_teams[ai_team].team_capsinarow = 0;
		return qtrue;
	}
	else
		return qfalse;
}


void bots_doQuadRally(int ai_team) {
	gentity_t	*ent;
	int			i;

	if (g_bots_quadrallies.integer) {
		for (i = 0; i < g_maxclients.integer; i++) {
			ent = g_entities + i;

			if (!ent->inuse || !ent->client)
				continue; 
			if (ent->bots_team != ai_team)
				continue; 
			if (ent->bots_class == BOTS_CAPTAIN)
				continue; 

			if (ent->client->pers.classlevel < 3)
				ent->client->pers.classlevel++;

			if (ent->client->ps.powerups[PW_QUAD] != 0)
				ent->client->ps.powerups[PW_QUAD] += 10000;
			else
				ent->client->ps.powerups[PW_QUAD] = level.time + 10000;

		}
		
		BOTS_cp_console(-1, "QUAD RALLY !!!");
	}
}


void bots_QuadRally() {
	if (bots_getQuadRally(TEAM_RED))
		bots_doQuadRally(TEAM_BLUE);
	else if (bots_getQuadRally(TEAM_BLUE))
		bots_doQuadRally(TEAM_RED);
}


void bots_InitTeams() {
	int		i;


	for (i = 0; i < BOTS_MAX_TEAMS; i++) {
		bots_teams[i].members = 0;
		bots_teams[i].team_capsinarow = 0;
		bots_teams[i].team_impeachvotes = 0;
		bots_teams[i].team_promos = 0;
		bots_teams[i].team_techpoints = 0;
		bots_teams[i].hasprotect = qfalse;

		bots_teamkeys[i].capkeydropped = 0;
		bots_teamkeys[i].techkeydropped = 0;

		memset(&bots_teams[i].sci_upgrades, '\0', sizeof(bots_sci_upgrades_t));

		bots_teams[i].cachedCaptain		= NULL;
		bots_teams[i].cachedScientist	= NULL;
	}

	bots_info.keychecktime = 0;
	bots_info.fairwarning = 0;
	bots_info.startgame = 0;
	bots_info.mapmode = 0;
	bots_info.goalmode = 0;
}


void bots_setCaptainLostKey(int ai_team, qboolean ab_clear) {
	if (ab_clear)
		bots_teamkeys[ai_team].capkeydropped = 0;
	else
		bots_teamkeys[ai_team].capkeydropped = level.time;
}

float bots_getCaptainLostKey(int ai_team) {
	return bots_teamkeys[ai_team].capkeydropped;
}

void bots_setScientistLostKey(int ai_team, qboolean ab_clear) {
	if (ab_clear)
		bots_teamkeys[ai_team].techkeydropped = 0;
	else
		bots_teamkeys[ai_team].techkeydropped = level.time;
}

float bots_getScientistLostKey(int ai_team) {
	return bots_teamkeys[ai_team].techkeydropped;
}


void bots_AddScore_Tech( gentity_t *ent, int ai_tech ) {
	if ( !ent->client ) {
		return;
	}
	// no scoring during pre-match warmup
	if ( level.warmupTime ) {
		return;
	}

	if (!bots_getScientist(ent->bots_team))
		return;
	
	if (!bots_fairTeams(ent->bots_team))
		return;
	
	bots_teams[ent->bots_team - 1].team_techpoints += ai_tech;
}

void bots_AddScore_Promo( gentity_t *ent, int ai_score, int ai_type, int ai_promos ) {
	if ( !ent->client ) {
		return;
	}
	// no scoring during pre-match warmup
	if ( level.warmupTime ) {
		return;
	}

	if (!bots_fairTeams(ent->bots_team))
		return;
	
 	if (bots_getCaptain(ent->bots_team))
		bots_teams[ent->bots_team - 1].team_promos += ai_promos;

	bots_AddScore_Tech(ent, ai_promos);
	bots_AddScore(ent, ai_score, ai_type,ent->r.currentOrigin);

	BOTS_compute_captain_level(ent->client->sess.sessionTeam);
}

void bots_AddScore( gentity_t *ent, int ai_score, int ai_type, vec3_t origin ) {

	int		score = ai_score;

	if ( !ent->client ) {
		return;
	}
	// no scoring during pre-match warmup
	if ( level.warmupTime ) {
		return;
	}

	if (!bots_fairTeams(ent->bots_team))
		return;
	
	if (ai_type == BOTS_SCORE_FRAG) {
		ent->client->ps.persistant[PERS_FRAGS] += 1;
		level.teamFrags[ ent->client->ps.persistant[PERS_TEAM] ] += 1;

		score = 1;
	}
	else if (ai_type == BOTS_SCORE_POINT) {
		ent->client->ps.persistant[PERS_SCORE] += ai_score;
	}
	else if (ai_type == BOTS_SCORE_FRAGPT) {
		ent->client->ps.persistant[PERS_FRAGS] += 1;
		ent->client->ps.persistant[PERS_SCORE] += ai_score;
		level.teamFrags[ ent->client->ps.persistant[PERS_TEAM] ] += 1;
	}
	else if (ai_type == BOTS_SCORE_CAPKILL) {
		ent->client->ps.persistant[PERS_SCORE] += ai_score;
		level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += 1;
	}
	else if (ai_type == BOTS_SCORE_TEAM) {
		ent->client->ps.persistant[PERS_SCORE] += ai_score;
		level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += ai_score;
	}
	else if (ai_type == BOTS_SCORE_ALL) {
		ent->client->ps.persistant[PERS_FRAGS] += ai_score;
		ent->client->ps.persistant[PERS_SCORE] += ai_score;
		level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += 1;
	}
	else if (ai_type == BOTS_SCORE_PENALTY) {
		level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] -= 1;
		score = -1;
	}
	else {
		ent->client->ps.persistant[PERS_FRAGS] += 1;
		level.teamFrags[ ent->client->ps.persistant[PERS_TEAM] ] += 1;
		score = 1;
	}

	ScorePlum(ent, origin, score);

	CalculateRanks();
}

void BOTS_set_impeach(gentity_t* ent)
{
	char* teamString;
	int   team = ent->client->sess.sessionTeam;

	if (!bots_teams[team - 1].members)
		return;

	// record the vote
	if (!ent->client->pers.impeachvote)
	{
		bots_teams[team - 1].team_impeachvotes++;
		ent->client->pers.impeachvote = qtrue;
	}

	if (team == TEAM_RED)
		teamString = va("Male");
	else
		teamString = va("Female");

	G_Printf(S_COLOR_YELLOW "%i people have voted to impeach the %s captain.", 
			 bots_teams[team - 1].team_impeachvotes, teamString);

	// check to see i
	if(bots_teams[team - 1].team_impeachvotes / bots_teams[team - 1].members > .75)
	{
//		char	   netname[30];
		gentity_t* captain = bots_getCaptain(team);

		// remove the stupid ass colors losers use
		//strcpy(netname, captain->client->pers.netname);
		//Q_CleanStr(netname);
		BOTS_cp_console(-1, va("%s has been impeached.", captain->client->pers.netname));
		captain->client->pers.impeached = qtrue;
		bots_ChangeClass (captain, BOTS_BODYGUARD);
	}
}

int* BOTS_get_promotech_points(int team)
{
	static points[2];

	points[0] = bots_teams[team - 1].team_promos;
	points[1] = bots_teams[team - 1].team_techpoints;

	return points;
}

void BOTS_set_promotech_points(int team, int points[2])
{
        if (points[0] >= 0)
                bots_teams[team - 1].team_promos         = points[0];
        else {
                bots_teams[team - 1].team_promos         = 0;
                G_Printf("BOTS_set_promotech_points: promo points clamped to 0 (%i)",points[0]);
        }

        if (points[1] >= 0)
                bots_teams[team - 1].team_techpoints = points[1];
        else {
                bots_teams[team - 1].team_techpoints = 0;
                G_Printf("BOTS_set_promotech_points: tech points clamped to 0 (%i)",points[1]);
        }
        BOTS_compute_captain_level(team);
}

void BOTS_compute_captain_level(int team)
{
	gentity_t* captain = bots_getCaptain(team);
	int i;
	qboolean alone = qtrue;
	int lowest_level = 4;

	if (!captain)
		return;

	for (i = 0; i < level.maxclients; i++)
	{
		gentity_t* player = g_entities + i;

		if (!player->client)
			continue;
		if (player->client->sess.sessionTeam != team)
			continue;
		if (player == captain)
			continue;
		if (player->client->pers.classlevel >= lowest_level) {
			alone = qfalse;
			continue;
		}

		lowest_level = player->client->pers.classlevel;
		alone = qfalse;
	}

	if (alone)
		captain->client->pers.classlevel = 0;
	else
		captain->client->pers.classlevel = lowest_level;
}


void BOTS_tech(int clientNum, qboolean ab_techall)
{
	gentity_t*	ent = g_entities + clientNum;
	int			i,j;
	int			teamdata[9][10];
	gentity_t*	player;
	char*		stats;

	if (!ent->client)
		return;

	if (ent->bots_class != BOTS_SCIENTIST)
	{
		BOTS_cp_console(clientNum, "Only the Scientist can issue tech command.");
		return;
	}

	// Brain dead fix ;)
	for (i = 1; i < BOTS_MAX_CLASSES; i++)
		for (j = 1; j < (MAX_SCI_UPGRADES+1); j++) {
			teamdata[i][j] = bots_teams[ent->bots_team - 1].sci_upgrades.classes[i-1].levels[j-1];
		}

	// Those va() are horrible... lots of useless string copies all around
	stats =   va("Class           WD FR RA AA HL SP JU ST SL\n");
	stats = va("%s-----           -- -- -- -- -- -- -- -- --\n", stats);
	stats = va("%sBodyguard       %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i \n", stats, teamdata[2][SCI_WEAPON_DAMAGE], teamdata[2][SCI_WEAPON_FIRERATE], teamdata[2][SCI_WEAPON_REDUCEDAMMO], teamdata[2][SCI_ARMOR_ADVANCED], teamdata[2][SCI_GENETIC_HEALTH], teamdata[2][SCI_GENETIC_SPEED], teamdata[2][SCI_GENETIC_JUMP], teamdata[2][SCI_GENETIC_STRENGTH], teamdata[2][SCI_GENETIC_STEALTH]);
	stats = va("%sSniper          %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i \n", stats, teamdata[3][SCI_WEAPON_DAMAGE], teamdata[3][SCI_WEAPON_FIRERATE], teamdata[3][SCI_WEAPON_REDUCEDAMMO], teamdata[3][SCI_ARMOR_ADVANCED], teamdata[3][SCI_GENETIC_HEALTH], teamdata[3][SCI_GENETIC_SPEED], teamdata[3][SCI_GENETIC_JUMP], teamdata[3][SCI_GENETIC_STRENGTH], teamdata[3][SCI_GENETIC_STEALTH]);
	stats = va("%sSoldier         %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i \n", stats, teamdata[4][SCI_WEAPON_DAMAGE], teamdata[4][SCI_WEAPON_FIRERATE], teamdata[4][SCI_WEAPON_REDUCEDAMMO], teamdata[4][SCI_ARMOR_ADVANCED], teamdata[4][SCI_GENETIC_HEALTH], teamdata[4][SCI_GENETIC_SPEED], teamdata[4][SCI_GENETIC_JUMP], teamdata[4][SCI_GENETIC_STRENGTH], teamdata[4][SCI_GENETIC_STEALTH]);
	stats = va("%sBerzerker       %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i \n", stats, teamdata[5][SCI_WEAPON_DAMAGE], teamdata[5][SCI_WEAPON_FIRERATE], teamdata[5][SCI_WEAPON_REDUCEDAMMO], teamdata[5][SCI_ARMOR_ADVANCED], teamdata[5][SCI_GENETIC_HEALTH], teamdata[5][SCI_GENETIC_SPEED], teamdata[5][SCI_GENETIC_JUMP], teamdata[5][SCI_GENETIC_STRENGTH], teamdata[5][SCI_GENETIC_STEALTH]);
	stats = va("%sInfiltrator     %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i \n", stats, teamdata[6][SCI_WEAPON_DAMAGE], teamdata[6][SCI_WEAPON_FIRERATE], teamdata[6][SCI_WEAPON_REDUCEDAMMO], teamdata[6][SCI_ARMOR_ADVANCED], teamdata[6][SCI_GENETIC_HEALTH], teamdata[6][SCI_GENETIC_SPEED], teamdata[6][SCI_GENETIC_JUMP], teamdata[6][SCI_GENETIC_STRENGTH], teamdata[6][SCI_GENETIC_STEALTH]);
	stats = va("%sKamikazee       %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i \n", stats, teamdata[7][SCI_WEAPON_DAMAGE], teamdata[7][SCI_WEAPON_FIRERATE], teamdata[7][SCI_WEAPON_REDUCEDAMMO], teamdata[7][SCI_ARMOR_ADVANCED], teamdata[7][SCI_GENETIC_HEALTH], teamdata[7][SCI_GENETIC_SPEED], teamdata[7][SCI_GENETIC_JUMP], teamdata[7][SCI_GENETIC_STRENGTH], teamdata[7][SCI_GENETIC_STEALTH]);
	stats = va("%sNurse           %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i  %1i \n", stats, teamdata[8][SCI_WEAPON_DAMAGE], teamdata[8][SCI_WEAPON_FIRERATE], teamdata[8][SCI_WEAPON_REDUCEDAMMO], teamdata[8][SCI_ARMOR_ADVANCED], teamdata[8][SCI_GENETIC_HEALTH], teamdata[8][SCI_GENETIC_SPEED], teamdata[8][SCI_GENETIC_JUMP], teamdata[8][SCI_GENETIC_STRENGTH], teamdata[8][SCI_GENETIC_STEALTH]);
	stats = va("bots_table \"%s\"", stats);

	if (ab_techall) {
		for (i = 0; i < g_maxclients.integer; i++)
		{
			player = &g_entities[i];
			if (!player->client)
				continue; 
			if (!player->client->pers.techmsg)
				continue;
			if (player->client->sess.sessionTeam == TEAM_SPECTATOR)
				continue;
			if (!OnSameTeam (ent, player))
				continue;

			if (player->client->pers.techmsg)
				trap_SendServerCommand( player->s.clientNum, stats);
		}
	}
	else
		trap_SendServerCommand( clientNum, stats);
}


void BOTS_transferPromos(int ai_from, int ai_to) 
{
	if (bots_teams[ai_from - 1].team_promos == 0)
		bots_teams[ai_to - 1].team_promos += 2;
	else
		bots_teams[ai_to - 1].team_promos += bots_teams[ai_from - 1].team_promos;
	
	bots_teams[ai_from - 1].team_promos = 0;
}


void BOTS_transferTech(int ai_from, int ai_to) 
{
	if (bots_teams[ai_from - 1].team_techpoints == 0)
		bots_teams[ai_to - 1].team_techpoints += 2;
	else
		bots_teams[ai_to - 1].team_techpoints += bots_teams[ai_from - 1].team_techpoints;

	bots_teams[ai_from - 1].team_techpoints = 0;
}


//
// Scientist research menu support functions.
//

static int BOTS_getRequiredTechPoints(int iLevel) {
	switch (iLevel) {
	case 0:
		return 1;
	case 1:
		return 2;
	case 2:
		return 3;
	case 3:
		return 4;
	default:
		return 0;
	}
}

const char *bots_class2string(int iClass) {

	// I'm glad someone already typed all that :p
	switch (iClass) {
	case 1:
		return CLASS1;
	case 2:
		return CLASS2;
	case 3:
		return CLASS3;
	case 4:
		return CLASS4;
	case 5:
		return CLASS5;
	case 6:
		return CLASS6;
	case 7:
		return CLASS7;
	case 8:
		return CLASS8;
	case 9:
		return CLASS9;
	default:
		return "Unknown";
	}
}

const char *bots_upgrade2string(int iUpgrade) {
	switch (iUpgrade) {
	case SCI_WEAPON_DAMAGE:
		return "Weapon Damage";
	case SCI_WEAPON_FIRERATE:
		return "Weapon Firing Rate";
	case SCI_WEAPON_REDUCEDAMMO:
		return "Reduced Ammo";
	case SCI_ARMOR_ADVANCED:
		return "Armor";
	case SCI_GENETIC_HEALTH:
		return "Health";
	case SCI_GENETIC_SPEED:
		return "Speed";
	case SCI_GENETIC_JUMP:
		return "Jumping";
	case SCI_GENETIC_STRENGTH:
		return "Strength";
	case SCI_GENETIC_STEALTH:
		return "Stealth";
	default:
		return "N/A";
	}
}

static void BOTS_informClass(int iTeam, int iClass, int iUpgrade, int iLevel) {
	gentity_t	*ent = g_entities;
	int			i;

	for (i = 0; i < g_maxclients.integer; i++) {
		if ((ent->inuse && ent->client && ent->bots_team == iTeam) && 
			(ent->bots_class == iClass || ent->bots_class == BOTS_SCIENTIST)) {
			BOTS_cp_console(ent - g_entities, va("%s %s has been upgraded to level %i.", bots_class2string(iClass),bots_upgrade2string(iUpgrade), iLevel));
		}
		ent++;
	}
}


static void BOTS_performClassUpgrade(gentity_t *ent, int iClass, int iUpgrade) {
	bots_team_t *p = &bots_teams[ent->bots_team - 1];

	// Wouldn't it be good to throw some checkings here and there? Hmm?
	iClass--; iUpgrade--;
	p->team_techpoints -= BOTS_getRequiredTechPoints(p->sci_upgrades.classes[iClass].levels[iUpgrade]);
	p->sci_upgrades.classes[iClass].levels[iUpgrade]++;
	BOTS_informClass(ent->bots_team, iClass+1, iUpgrade+1, p->sci_upgrades.classes[iClass].levels[iUpgrade]);
}


void BOTS_scientistResearchClass(int clientNum, char *as_class) {
	gentity_t	*ent = g_entities + clientNum;
	int			iClass;


	if (!ent->client)
		return;

	if (Q_strncmp (as_class, "bodyguard", 9) == 0 )
		iClass = BOTS_BODYGUARD;
	else if (Q_strncmp (as_class, "sniper", 6) == 0 )
		iClass = BOTS_SNIPER;
	else if (Q_strncmp (as_class, "soldier", 7) == 0 )
		iClass = BOTS_SOLDIER;
	else if (Q_strncmp (as_class, "berzerker", 9) == 0 )
		iClass = BOTS_BERZERKER;
	else if (Q_strncmp (as_class, "infiltrator", 11) == 0 )
		iClass = BOTS_INFILTRATOR;
	else if (Q_strncmp (as_class, "kamikazee", 9) == 0 )
		iClass = BOTS_KAMIKAZEE;
	else if (Q_strncmp (as_class, "nurse", 5) == 0 )
		iClass = BOTS_NURSE;
	else 
		return;

	BOTS_performClassUpgrade(ent, iClass, bots_teams[ent->bots_team - 1].nextResearchArea);
}



void BOTS_setResearch(int clientNum, char *as_research) {
	gentity_t*	ent = g_entities + clientNum;

	// If someone has enough patience, remove those damn strlen!
	// :p
	if (Q_strncmp (as_research, "weapondamage", strlen ("weapondamage")) == 0 )
		bots_teams[ent->bots_team - 1].nextResearchArea = SCI_WEAPON_DAMAGE;
	else if (Q_strncmp (as_research, "firerate", strlen ("firerate")) == 0 )
		bots_teams[ent->bots_team - 1].nextResearchArea = SCI_WEAPON_FIRERATE;
	else if (Q_strncmp (as_research, "reducedammo", strlen ("reducedammo")) == 0 )
		bots_teams[ent->bots_team - 1].nextResearchArea = SCI_WEAPON_REDUCEDAMMO;
	else if (Q_strncmp (as_research, "advancedarmor", strlen ("advancedarmor")) == 0 )
		bots_teams[ent->bots_team - 1].nextResearchArea = SCI_ARMOR_ADVANCED;
	else if (Q_strncmp (as_research, "health", strlen ("health")) == 0 )
		bots_teams[ent->bots_team - 1].nextResearchArea = SCI_GENETIC_HEALTH;
	else if (Q_strncmp (as_research, "speed", strlen ("speed")) == 0 )
		bots_teams[ent->bots_team - 1].nextResearchArea = SCI_GENETIC_SPEED;
	else if (Q_strncmp (as_research, "jump", strlen ("jump")) == 0 )
		bots_teams[ent->bots_team - 1].nextResearchArea = SCI_GENETIC_JUMP;
	else if (Q_strncmp (as_research, "strength", strlen ("strength")) == 0 )
		bots_teams[ent->bots_team - 1].nextResearchArea = SCI_GENETIC_STRENGTH;
	else if (Q_strncmp (as_research, "stealth", strlen ("stealth")) == 0 )
		bots_teams[ent->bots_team - 1].nextResearchArea = SCI_GENETIC_STEALTH;
}

// Shit, i forgot sprintf wasn't available... here's a quick fix (better than rewrite the other crap)
static int my_sprintf(char *dest, const char *fmt, ...) {
	int		len;
	va_list		argptr;

	va_start (argptr,fmt);
	len = vsprintf (dest,fmt,argptr);
	va_end (argptr);
	return len;
}

static int BOTS_checkClassUpgrade(bots_sci_upgrades_t *p, int iClass, int iUpgrade, int iTechpts, int iSciLvl, const char *prefix, char *s) {
	int iLevel = p->classes[iClass-1].levels[iUpgrade-1];


	if ( iLevel < 4 && iSciLvl > iLevel && iTechpts > BOTS_getRequiredTechPoints(iLevel) ) 
		return my_sprintf(s,"\\%s\\1\\%sx\\%i", prefix, prefix, iLevel);
	else
		return my_sprintf(s,"\\%s\\0\\%sx\\%i", prefix, prefix, iLevel);
}


// Ok, this is prone to the same race bug as the promo thingy
// but i'm too lazy to cure the symptom again. The Right Thing would be to that in a cleaner fashion.	
void BOTS_scientistResearchClass_setup(int clientNum)
{
	bots_sci_upgrades_t	*p;
	gentity_t			*ent = g_entities + clientNum;
	int					iSciLvl, iTechpts, iUpgrade;
	char				
		buffer[4096], // keep finger crossed :)
		*s;



	if (!ent->client)
		return;

	iSciLvl = ent->client->pers.classlevel;

	// check level
	if (iSciLvl < 1) {
		BOTS_cp_console( clientNum, "You must be at least level 1 to research.");
		return;
	}

	iTechpts = bots_teams[ent->bots_team - 1].team_techpoints;
	iUpgrade = bots_teams[ent->bots_team - 1].nextResearchArea;
	p = &bots_teams[ent->bots_team - 1].sci_upgrades;
	
	s = buffer;
	s += my_sprintf(s,"\\tp\\%i", iTechpts);

	s += BOTS_checkClassUpgrade(p, BOTS_BODYGUARD, iUpgrade, iTechpts, iSciLvl,		"bb", s);
	s += BOTS_checkClassUpgrade(p, BOTS_SNIPER, iUpgrade, iTechpts, iSciLvl,		"bs", s);
	s += BOTS_checkClassUpgrade(p, BOTS_SOLDIER, iUpgrade, iTechpts, iSciLvl,		"bd", s);
	s += BOTS_checkClassUpgrade(p, BOTS_BERZERKER, iUpgrade, iTechpts, iSciLvl,		"bz", s);
	s += BOTS_checkClassUpgrade(p, BOTS_INFILTRATOR, iUpgrade, iTechpts, iSciLvl,	"bi", s);
	s += BOTS_checkClassUpgrade(p, BOTS_KAMIKAZEE, iUpgrade, iTechpts, iSciLvl,		"bk", s);
	s += BOTS_checkClassUpgrade(p, BOTS_NURSE, iUpgrade, iTechpts, iSciLvl,			"bn", s);

	// set the menu list
	trap_SetConfigstring(CS_BOTS_MENU + clientNum, buffer);

	// tell cgame we are done so it can send to the UI.
	trap_SendServerCommand(clientNum, "classresearch_data_complete");
}



// And ad hoc helper
static const char *BOTS_checkUpgrade(bots_sci_upgrades_t *p, int iLevel, int iUpgrade) {
	int i;


	// Scan every class (but captain & sci) for upgradability
	iUpgrade--;
	for (i = BOTS_BODYGUARD; i < BOTS_SCIENTIST; i++) 
		if (p->classes[i-1].levels[iUpgrade] < iLevel)
			return "1";

	return "2";
}


void BOTS_scientistResearch_setup(int clientNum)
{
	char				*create_string;
	int					iTeam, iScilvl;
	bots_sci_upgrades_t *p;
	gentity_t			*ent = g_entities + clientNum;


	if (!ent->client)
		return;

	iScilvl = ent->client->pers.classlevel;
	iTeam	= ent->bots_team - 1;
	p		= &bots_teams[iTeam].sci_upgrades;

	// check level
	if (iScilvl < 1)
	{
		BOTS_cp_console( clientNum, "You must be at least level 1 to research.");
		return;
	}

	if (bots_teams[iTeam].team_techpoints < 1)
	{
		BOTS_cp_console( clientNum, "You don't have any tech points.");
		return;
	}

	if (ent->techkey) {
		BOTS_cp_console( clientNum, "You are not carrying the Tech Key.");
		return;
	}

	// Check each upgrade to see if it is still available
	// Gah! I hate those va()
	create_string = va("\\tp\\%i\\wd\\%s\\wf\\%s\\wa\\%s\\aa\\%s\\gh\\%s\\gp\\%s\\gj\\%s\\gs\\%s\\gi\\%s", 
			bots_teams[iTeam].team_techpoints,
			BOTS_checkUpgrade(p, iScilvl, SCI_WEAPON_DAMAGE),
			BOTS_checkUpgrade(p, iScilvl, SCI_WEAPON_FIRERATE),
			BOTS_checkUpgrade(p, iScilvl, SCI_WEAPON_REDUCEDAMMO),
			BOTS_checkUpgrade(p, iScilvl, SCI_ARMOR_ADVANCED),
			BOTS_checkUpgrade(p, iScilvl, SCI_GENETIC_HEALTH),
			BOTS_checkUpgrade(p, iScilvl, SCI_GENETIC_SPEED),
			BOTS_checkUpgrade(p, iScilvl, SCI_GENETIC_JUMP),
			BOTS_checkUpgrade(p, iScilvl, SCI_GENETIC_STRENGTH),
			BOTS_checkUpgrade(p, iScilvl, SCI_GENETIC_STEALTH)
			);

	// set the menu list
	trap_SetConfigstring(CS_BOTS_MENU + clientNum, create_string);

	// tell cgame we are done so it can send to the UI.
	trap_SendServerCommand(clientNum, "genresearch_data_complete");
}

// conquer stuff
static int conquer_flag[2] = {-999, -999};

// resets our global to unconquered
void BOTS_conquer_clear(int team)
{
	if (team == TEAM_BLUE)
		BOTS_cp_console(-1, "BLUE Capture Pad has been released.");
	else
		BOTS_cp_console(-1, "RED Capture Pad has been released.");

	conquer_flag[team - 1] = -999;
}

// retrieves the clientnum of the person who conquered the pad/flag
int BOTS_conquer_get(int team) {
	return conquer_flag[team - 1];
}

// sets our global to the clientnum of the person who conquered the pad/flag
void BOTS_conquer_set(int team, int clientNum)
{
	gentity_t* player = g_entities + clientNum;

	if ((!player->client) || BOTS_conquer_get(team) != -999)
		return;

	if (team == TEAM_BLUE)
		BOTS_cp_console(-1, va("BLUE Capture Pad is conquered by %s.", player->client->pers.netname));
	else
		BOTS_cp_console(-1, va("RED Capture Pad is conquered by %s.", player->client->pers.netname));

	conquer_flag[team - 1] = clientNum;
}


void BOTS_return_PromoPoints(gentity_t* ent)
{
	int*	promotech;

	if (!ent->client)
		return;

	if (ent->client->pers.classlevel > 1 && ent->bots_class != BOTS_CAPTAIN)
	{
		promotech = BOTS_get_promotech_points(ent->client->sess.sessionTeam);

		// we want their classlevel - 1 because they get level 1 status when they
		// change classes
		promotech[0] += ent->client->pers.classlevel - 1;
		BOTS_set_promotech_points(ent->client->sess.sessionTeam, promotech);
	}
}


void BOTS_tech_stripe(int iTeam) {
	int i,j;

	iTeam--;

	// Decrement each upgrade for each class
	for (i = 0; i < BOTS_MAX_CLASSES; i++)
		for (j = 0; j < MAX_SCI_UPGRADES; j++) {
			if (bots_teams[iTeam].sci_upgrades.classes[i].levels[j] > 0)
				bots_teams[iTeam].sci_upgrades.classes[i].levels[j]--;
		}
}
